﻿namespace Resharper.Abstractions.Daemons
{
  using System;
  using System.Collections.Generic;
  using System.Diagnostics;
  using JetBrains.Annotations;
  using JetBrains.DocumentModel;
  using JetBrains.ReSharper.Daemon;
  using JetBrains.ReSharper.Psi;
  using JetBrains.ReSharper.Psi.CSharp;
  using JetBrains.ReSharper.Psi.CSharp.Tree;
  using JetBrains.ReSharper.Psi.Tree;
  using JetBrains.Util;
  using Resharper.Abstractions.CodeDom.TreeNodes;

  /// <summary>Defines the <see cref="DaemonBase"/> class.</summary>
  public abstract class DaemonBase : IDaemonStage, IDaemonStageProcess
  {
    #region Constants and Fields

    /// <summary>The empty parameters field.</summary>
    private static readonly object[] emptyParameters = new object[0];

    #endregion

    #region Implemented Interfaces

    #region IDaemonStageProcess

    /// <summary>
    /// Whole daemon process
    /// </summary>
    IDaemonProcess IDaemonStageProcess.DaemonProcess
    {
      get
      {
        return this.CurrentProcess;
      }
    }

    #endregion

    #endregion

    #region Properties

    /// <summary>
    /// Gets or sets the current process.
    /// </summary>
    /// <value>The current process.</value>
    internal IDaemonProcess CurrentProcess { get; set; }

    /// <summary>
    /// Gets or sets the highlights.
    /// </summary>
    /// <value>The highlights.</value>
    internal ICollection<HighlightBase> Highlights { get; set; }

    #endregion

    #region Public Methods

    /// <summary>Creates the highlight.</summary>
    /// <typeparam name="T"></typeparam>
    /// <returns>Returns the highlight.</returns>
    [NotNull]
    public T CreateHighlight<T>() where T : HighlightBase, new()
    {
      var result = new T();

      this.Highlights.Add(result);

      return result;
    }

    #endregion

    #region Implemented Interfaces

    #region IDaemonStage

    /// <summary>Creates a code analysis process corresponding to this stage for analysing a file.</summary>
    /// <param name="process">The process.</param>
    /// <param name="processKind">Kind of the process.</param>
    /// <returns>Code analysis process to be executed or <c>null</c> if this stage is not available for this file.</returns>
    IDaemonStageProcess IDaemonStage.CreateProcess(IDaemonProcess process, DaemonProcessKind processKind)
    {
      var constructor = this.GetType().GetConstructor(Type.EmptyTypes);
      Debug.Assert(constructor != null, "constructor != null");

      var result = (DaemonBase)constructor.Invoke(emptyParameters);

      result.CurrentProcess = process;

      return result;
    }

    /// <summary>Check the error stripe indicator necessity for this stage after processing given <paramref name="sourceFile"/></summary>
    /// <param name="sourceFile">The source file.</param>
    /// <returns>Returns the error stripe.</returns>
    ErrorStripeRequest IDaemonStage.NeedsErrorStripe(IPsiSourceFile sourceFile)
    {
      return ErrorStripeRequest.STRIPE_AND_ERRORS;
    }

    #endregion

    #region IDaemonStageProcess

    /// <summary>Executes the process.
    /// The process should check for <see cref="P:JetBrains.ReSharper.Daemon.IDaemonProcess.InterruptFlag"/> periodically (with intervals less than 100 ms)
    /// and throw <see cref="T:JetBrains.Application.Progress.ProcessCancelledException"/> if it is true.
    /// Failing to do so may cause the program to prevent user from typing while analysing the code.
    /// Stage results should be passed to <param name="commiter"/>. If DaemonStageResult is <c>null</c>, it means that no highlightings available</summary>
    void IDaemonStageProcess.Execute(Action<DaemonStageResult> commiter)
    {
      var sourceFile = this.CurrentProcess.SourceFile;

      if (!this.CanExecute(new SourceFile(sourceFile)))
      {
        return;
      }

      var manager = PsiManager.GetInstance(this.CurrentProcess.Solution);

      var file = manager.GetPsiFile(sourceFile, CSharpLanguage.Instance) as ICSharpFile;
      if (file == null)
      {
        return;
      }

      this.Highlights = new List<HighlightBase>();

      var processor = new RecursiveElementProcessor(this.ProcessTreeNode);
      processor.Process(file);

      var infos = new List<HighlightingInfo>();

      foreach (var highlightBase in this.Highlights)
      {
        highlightBase.InternalDocument = this.CurrentProcess.Document;

        var documentRange = new DocumentRange(this.CurrentProcess.Document, new TextRange(highlightBase.Range.Start, highlightBase.Range.End));

        var info = new HighlightingInfo(documentRange, highlightBase);

        infos.Add(info);
      }

      commiter(new DaemonStageResult(infos));
    }

    #endregion

    #endregion

    #region Methods

    /// <summary>Determines whether this instance can execute the specified source file.</summary>
    /// <param name="sourceFile">The source file.</param>
    /// <returns><c>true</c> if this instance can execute the specified source file; otherwise, <c>false</c>.</returns>
    protected virtual bool CanExecute(SourceFile sourceFile)
    {
      return true;
    }

    /// <summary>Processes the specified tree node.</summary>
    /// <param name="treeNode">The tree node.</param>
    protected abstract void Process(TreeNode treeNode);

    /// <summary>Processes the tree node.</summary>
    /// <param name="treeNode">The tree node.</param>
    protected virtual void ProcessTreeNode(ITreeNode treeNode)
    {
      try
      {
        this.Process(new TreeNode(treeNode));
      }
      catch
      {
        return;
      }
    }

    #endregion
  }
}